﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Sockets;
using System.Net;
using System.Security.AccessControl;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Newtonsoft.Json.Linq;
using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Configuration;
using static System.Net.Mime.MediaTypeNames;
using System.Xml;
using SiemensPLC.UAClientHelper;
using System.Collections;
using System.Xml.Linq;

namespace OPCUA
{
    public partial class Client : Form
    {

        static Session session;
        static SessionReconnectHandler reconnectHandler;
        static string currentSessionMessage = "";

        delegate void connectCallBack(bool success, string msg);
        private async void initClient(string serverUrl,uint sessionTimeout, string username, string password ,connectCallBack callBack)
        {
            ApplicationInstance app = new ApplicationInstance();
            app.ApplicationType = Opc.Ua.ApplicationType.Client;
            app.ConfigSectionName = "Client";

            try
            {
                //app.LoadApplicationConfiguration(false).Wait(); //加载XML配置文件 Client.Config.xml
                //var config = app.ApplicationConfiguration;

                var config =  await CreateClientConfiguration();  //或者用此方法加载配置


                app.CheckApplicationInstanceCertificate(false, 0).Wait(); 

                config.CertificateValidator.CertificateValidation += certificateValidation; //验证服务器证书

                var endpointDescription = CoreClientUtils.SelectEndpoint(config, serverUrl, true, 15000);    //设置15S连接超时
                var endpointConfiguration = EndpointConfiguration.Create(config);
                var endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);

                if(username == null)
                {
                    session = await Session.Create(config, endpoint,
                        false, true,
                        config.ApplicationName,
                        sessionTimeout,
                        null, null);
                }
                else    //连接设置需要用户名跟密码
                {
                    session = await Session.Create(config, endpoint,
                        false, true,
                        config.ApplicationName,
                        sessionTimeout,
                        new UserIdentity(username, password), null); 

                }




                if (session == null)
                {
                    callBack(false, "连接超时");
                    return;
                }

                callBack(true, null);//成功连接到OPC服务器
            }
            catch (Exception ex)
            {
                callBack(false, ex.Message);
                return;
            }


            session.KeepAlive += sessionKeepAlive;



        }


        private static async Task<ApplicationConfiguration> CreateClientConfiguration()
        {
            // The application configuration can be loaded from any file.
            // ApplicationConfiguration.Load() method loads configuration by looking up a file path in the App.config.
            // This approach allows applications to share configuration files and to update them.
            // This example creates a minimum ApplicationConfiguration using its default constructor.
            ApplicationConfiguration configuration = new ApplicationConfiguration();

            // Step 1 - Specify the client identity.
            configuration.ApplicationName = "UA Client 1500";
            configuration.ApplicationType = ApplicationType.Client;
            configuration.ApplicationUri = "urn:MyClient"; //Kepp this syntax
            configuration.ProductUri = "SiemensAG.IndustryOnlineSupport";

            // Step 2 - Specify the client's application instance certificate.
            // Application instance certificates must be placed in a windows certficate store because that is 
            // the best way to protect the private key. Certificates in a store are identified with 4 parameters:
            // StoreLocation, StoreName, SubjectName and Thumbprint.
            // When using StoreType = Directory you need to have the opc.ua.certificategenerator.exe installed on your machine

            configuration.SecurityConfiguration = new SecurityConfiguration();
            configuration.SecurityConfiguration.ApplicationCertificate = new CertificateIdentifier();
            configuration.SecurityConfiguration.ApplicationCertificate.StoreType = CertificateStoreType.X509Store;
            configuration.SecurityConfiguration.ApplicationCertificate.StorePath = "CurrentUser\\My";
            configuration.SecurityConfiguration.ApplicationCertificate.SubjectName = configuration.ApplicationName;

            // Define trusted root store for server certificate checks
            configuration.SecurityConfiguration.TrustedIssuerCertificates.StoreType = CertificateStoreType.X509Store;
            configuration.SecurityConfiguration.TrustedIssuerCertificates.StorePath = "CurrentUser\\Root";
            configuration.SecurityConfiguration.TrustedPeerCertificates.StoreType = CertificateStoreType.X509Store;
            configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath = "CurrentUser\\Root";

            // find the client certificate in the store.
            X509Certificate2 clientCertificate = await configuration.SecurityConfiguration.ApplicationCertificate.Find(true);

            // create a new self signed certificate if not found.
            if (clientCertificate == null)
            {
                // Get local interface ip addresses and DNS name
                List<string> localIps = GetLocalIpAddressAndDns();

                UInt16 keySize = 2048; //must be multiples of 1024
                UInt16 lifeTime = 24; //in month
                UInt16 algorithm = 1; //0 = SHA1; 1 = SHA256

                // this code would normally be called as part of the installer - called here to illustrate.
                // create a new certificate an place it in the current user certificate store.
                clientCertificate = CertificateFactory.CreateCertificate(
                    configuration.SecurityConfiguration.ApplicationCertificate.StoreType,
                    configuration.SecurityConfiguration.ApplicationCertificate.StorePath,
                    "pwd",
                    configuration.ApplicationUri,
                    configuration.ApplicationName,
                    null,
                    localIps,
                    keySize,
                    DateTime.Now,
                    lifeTime,
                    algorithm);

            }


            // Step 3 - Specify the supported transport quotas.
            // The transport quotas are used to set limits on the contents of messages and are
            // used to protect against DOS attacks and rogue clients. They should be set to
            // reasonable values.
            configuration.TransportQuotas = new TransportQuotas();
            configuration.TransportQuotas.OperationTimeout = 360000;
            configuration.TransportQuotas.MaxStringLength = 67108864;
            configuration.TransportQuotas.MaxByteStringLength = 16777216; //Needed, i.e. for large TypeDictionarys


            // Step 4 - Specify the client specific configuration.
            configuration.ClientConfiguration = new ClientConfiguration();
            configuration.ClientConfiguration.DefaultSessionTimeout = 360000;

            // Step 5 - Validate the configuration.
            // This step checks if the configuration is consistent and assigns a few internal variables
            // that are used by the SDK. This is called automatically if the configuration is loaded from
            // a file using the ApplicationConfiguration.Load() method.
            await configuration.Validate(ApplicationType.Client);

            return configuration;
        }

        private static List<string> GetLocalIpAddressAndDns()
        {
            List<string> localIps = new List<string>();
            var host = Dns.GetHostEntry(Dns.GetHostName());
            foreach (var ip in host.AddressList)
            {
                if (ip.AddressFamily == AddressFamily.InterNetwork)
                {
                    localIps.Add(ip.ToString());
                }
            }
            if (localIps.Count == 0)
            {
                throw new Exception("Local IP Address Not Found!");
            }
            localIps.Add(Dns.GetHostName());
            return localIps;
        }


        private void sessionKeepAlive(ISession session, KeepAliveEventArgs e)
        {
            if (e.Status != null && ServiceResult.IsBad(e.Status))
            {


                if (reconnectHandler == null)
                {
                    reconnectHandler = new SessionReconnectHandler(true, 10 * 1000); //重连
                }


                setCurrentSessionMsg("正在尝试重新连接");
                reconnectHandler.BeginReconnect(session, 1000, serverReconnectComplete);
            }
        }

        private void serverReconnectComplete(object sender, EventArgs e)//重连
        {
            setCurrentSessionMsg("连接成功");

            if (reconnectHandler.Session == null) return;


            if (!ReferenceEquals(session, reconnectHandler.Session))
            {
                var oldSession = session;
                session.KeepAlive -= sessionKeepAlive;

                session = reconnectHandler.Session as Session;
                session.KeepAlive += sessionKeepAlive;

                Utils.SilentDispose(oldSession);
            }

        }

        private static void certificateValidation(CertificateValidator sender, CertificateValidationEventArgs e) //接受服务器证书
        {
            e.Accept = true;
        }

        private static object ChangeType(DataValue currentValue, object value)
        {
            if (currentValue == null) return null;  //无法得知类型

            object result = currentValue.Value;


            switch (currentValue.WrappedValue.TypeInfo.BuiltInType)
            {
                case BuiltInType.Boolean:
                    {
                        result = Convert.ToBoolean(value);
                        break;
                    }

                case BuiltInType.SByte:
                    {
                        result = Convert.ToSByte(value);
                        break;
                    }

                case BuiltInType.Byte:
                    {
                        result = Convert.ToByte(value);
                        break;
                    }

                case BuiltInType.Int16:
                    {
                        result = Convert.ToInt16(value);
                        break;
                    }

                case BuiltInType.UInt16:
                    {
                        result = Convert.ToUInt16(value);
                        break;
                    }

                case BuiltInType.Int32:
                    {
                        result = Convert.ToInt32(value);
                        break;
                    }

                case BuiltInType.UInt32:
                    {
                        result = Convert.ToUInt32(value);
                        break;
                    }

                case BuiltInType.Int64:
                    {
                        result = Convert.ToInt64(value);
                        break;
                    }

                case BuiltInType.UInt64:
                    {
                        result = Convert.ToUInt64(value);
                        break;
                    }

                case BuiltInType.Float:
                    {
                        result = Convert.ToSingle(value);
                        break;
                    }

                case BuiltInType.Double:
                    {
                        result = Convert.ToDouble(value);
                        break;
                    }

                default:
                    {
                        //其他未知类型，写入可能会报错
                        result = value;
                        break;
                    }
            }

            return result;
        } //先读一遍，获取类型后再写
        private static object ChangeType(string typeString, object value) //根据类型转换值，再写
        {
            object result = null;


            typeString = typeString.ToLower();

            switch (typeString)
            {
                case "boolean":
                    {
                        result = Convert.ToBoolean(value);
                        break;
                    }

                case "bool":
                    {
                        result = Convert.ToBoolean(value);
                        break;
                    }

                case "sbyte":
                    {
                        result = Convert.ToSByte(value);
                        break;
                    }

                case "byte":
                    {
                        result = Convert.ToByte(value);
                        break;
                    }

                case "int16":
                    {
                        result = Convert.ToInt16(value);
                        break;
                    }

                case "uint16":
                    {
                        result = Convert.ToUInt16(value);
                        break;
                    }

                case "int":
                    {
                        result = Convert.ToInt32(value);
                        break;
                    }

                case "int32":
                    {
                        result = Convert.ToInt32(value);
                        break;
                    }

                case "uint32":
                    {
                        result = Convert.ToUInt32(value);
                        break;
                    }

                case "int64":
                    {
                        result = Convert.ToInt64(value);
                        break;
                    }

                case "unt64":
                    {
                        result = Convert.ToUInt64(value);
                        break;
                    }

                case "float":
                    {
                        result = Convert.ToSingle(value);
                        break;
                    }

                case "double":
                    {
                        result = Convert.ToDouble(value);
                        break;
                    }

                default:
                    {
                        //默认不做转换
                        result = value;
                        break;
                    }
            }

            return result;
        }
        private static ReferenceDescription GetReference(string value) //逐级查找节点，找到对应描述
        {
            value = value.Trim();
            ReferenceDescription currentDescription = null;
            string[] values = value.Split('.');
            ReferenceDescriptionCollection collection = BrowseNode(ObjectIds.ObjectsFolder);

            if (collection == null) return null; //通讯失败

            for (int i = 0; i < values.Length; i++)
            {
                try
                {
                    currentDescription = collection.Where(item => item.DisplayName == values[i]).FirstOrDefault();
                    if (currentDescription == null) continue;
                    collection = BrowseNode((NodeId)currentDescription.NodeId);
                }
                catch (Exception ex)
                {
                    return null;
                }
            }
            return currentDescription;
        }

        private static ReferenceDescriptionCollection BrowseNode(NodeId nodeId)
        {
            BrowseDescription nodeToBrowse1 = new BrowseDescription();
            nodeToBrowse1.NodeId = nodeId;
            nodeToBrowse1.BrowseDirection = BrowseDirection.Forward;
            nodeToBrowse1.ReferenceTypeId = ReferenceTypeIds.Aggregates;
            nodeToBrowse1.IncludeSubtypes = true;
            nodeToBrowse1.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable);
            nodeToBrowse1.ResultMask = (uint)BrowseResultMask.All;

            BrowseDescription nodeToBrowse2 = new BrowseDescription();
            nodeToBrowse2.NodeId = nodeId;
            nodeToBrowse2.BrowseDirection = BrowseDirection.Forward;
            nodeToBrowse2.ReferenceTypeId = ReferenceTypeIds.Organizes;
            nodeToBrowse2.IncludeSubtypes = true;
            nodeToBrowse2.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable);
            nodeToBrowse2.ResultMask = (uint)BrowseResultMask.All;

            BrowseDescriptionCollection nodeToBrowse = new BrowseDescriptionCollection();

            nodeToBrowse.Add(nodeToBrowse1);
            nodeToBrowse.Add(nodeToBrowse2);

            ReferenceDescriptionCollection references = Browse(session, nodeToBrowse, false);

            if (references != null)
                for (int i = 0; i < references.Count; i++)
                {
                    ReferenceDescription target = references[i];
                }

            return references;

        }


        /// <summary>
        /// Browses the address space and returns the references found. 获取节点信息
        /// </summary>
        /// <param name="session">The session.</param>
        /// <param name="nodesToBrowse">The set of browse operations to perform.</param>
        /// <param name="throwOnError">if set to <c>true</c> a exception will be thrown on an error.</param>
        /// <returns>
        /// The references found. Null if an error occurred.
        /// </returns>
        public static ReferenceDescriptionCollection Browse(Session session, BrowseDescriptionCollection nodesToBrowse, bool throwOnError)
        {
            try
            {
                ReferenceDescriptionCollection references = new ReferenceDescriptionCollection();
                BrowseDescriptionCollection unprocessedOperations = new BrowseDescriptionCollection();

                while (nodesToBrowse.Count > 0)
                {
                    // start the browse operation.
                    BrowseResultCollection results = null;
                    DiagnosticInfoCollection diagnosticInfos = null;

                    session.Browse(
                        null,
                        null,
                        0,
                        nodesToBrowse,
                        out results,
                        out diagnosticInfos);

                    ClientBase.ValidateResponse(results, nodesToBrowse);
                    ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse);

                    ByteStringCollection continuationPoints = new ByteStringCollection();

                    for (int ii = 0; ii < nodesToBrowse.Count; ii++)
                    {
                        // check for error.
                        if (StatusCode.IsBad(results[ii].StatusCode))
                        {
                            // this error indicates that the server does not have enough simultaneously active 
                            // continuation points. This request will need to be resent after the other operations
                            // have been completed and their continuation points released.
                            if (results[ii].StatusCode == StatusCodes.BadNoContinuationPoints)
                            {
                                unprocessedOperations.Add(nodesToBrowse[ii]);
                            }

                            continue;
                        }

                        // check if all references have been fetched.
                        if (results[ii].References.Count == 0)
                        {
                            continue;
                        }

                        // save results.
                        references.AddRange(results[ii].References);

                        // check for continuation point.
                        if (results[ii].ContinuationPoint != null)
                        {
                            continuationPoints.Add(results[ii].ContinuationPoint);
                        }
                    }

                    // process continuation points.
                    ByteStringCollection revisedContinuationPoints = new ByteStringCollection();

                    while (continuationPoints.Count > 0)
                    {
                        // continue browse operation.
                        session.BrowseNext(
                            null,
                            false,
                            continuationPoints,
                            out results,
                            out diagnosticInfos);

                        ClientBase.ValidateResponse(results, continuationPoints);
                        ClientBase.ValidateDiagnosticInfos(diagnosticInfos, continuationPoints);

                        for (int ii = 0; ii < continuationPoints.Count; ii++)
                        {
                            // check for error.
                            if (StatusCode.IsBad(results[ii].StatusCode))
                            {
                                continue;
                            }

                            // check if all references have been fetched.
                            if (results[ii].References.Count == 0)
                            {
                                continue;
                            }

                            // save results.
                            references.AddRange(results[ii].References);

                            // check for continuation point.
                            if (results[ii].ContinuationPoint != null)
                            {
                                revisedContinuationPoints.Add(results[ii].ContinuationPoint);
                            }
                        }

                        // check if browsing must continue;
                        continuationPoints = revisedContinuationPoints;
                    }

                    // check if unprocessed results exist.
                    nodesToBrowse = unprocessedOperations;
                }

                // return complete list.
                return references;
            }
            catch (Exception exception)
            {
                if (throwOnError)
                {
                    throw new ServiceResultException(exception, StatusCodes.BadUnexpectedError);
                }

                return null;
            }
        }


        #region Read/Write 读写方法
        /// <summary>Reads a node by node Id</summary>
        /// <param name="nodeIdString">The node Id as string</param>
        /// <returns>The read node</returns>
        /// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
        public Node ReadNode(String nodeIdString)
        {
            //Create a nodeId using the identifier string
            NodeId nodeId = new NodeId(nodeIdString);
            //Create a node
            Node node = new Node();
            try
            {
                //Read the dataValue
                node = session.ReadNode(nodeId);
                return node;
            }
            catch (Exception e)
            {
                //handle Exception here
                throw e;
            }
        }

        /// <summary>Reads values from node Ids</summary>
        /// <param name="nodeIdStrings">The node Ids as strings</param>
        /// <returns>The read values as strings</returns>
        /// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
        public List<string> ReadValues(List<String> nodeIdStrings)
        {
            List<NodeId> nodeIds = new List<NodeId>();
            List<Type> types = new List<Type>();
            List<object> values = new List<object>();
            List<ServiceResult> serviceResults = new List<ServiceResult>();
            foreach (string str in nodeIdStrings)
            {
                //Create a nodeId using the identifier string and add to list
                nodeIds.Add(new NodeId(str));
                //No need for types
                types.Add(null);
            }
            try
            {
                //Read the dataValues
                session.ReadValues(nodeIds, types, out values, out serviceResults);
                //check ServiceResults to 
                foreach (ServiceResult svResult in serviceResults)
                {
                    if (svResult.ToString() != "Good")
                    {
                        Exception e = new Exception(svResult.ToString());
                        throw e;
                    }
                }
                List<string> resultStrings = new List<string>();
                foreach (object result in values)
                {
                    if (result != null)
                    {
                        if (result.ToString() == "System.Byte[]")
                        {
                            string str = "";
                            str = BitConverter.ToString((byte[])result).Replace("-", ";");
                            resultStrings.Add(str);
                        }
                        if (result.ToString() == "System.String[]")
                        {
                            string str = "";
                            str = String.Join(";", (string[])result);
                            resultStrings.Add(str);
                        }
                        else if (result.ToString() == "System.Boolean[]")
                        {
                            string str = "";
                            foreach (Boolean intVar in (Boolean[])result)
                            {
                                str = str + ";" + intVar.ToString();
                            }
                            str = str.Remove(0, 1);
                            resultStrings.Add(str);
                        }
                        else if (result.ToString() == "System.Int16[]")
                        {
                            string str = "";
                            foreach (Int16 intVar in (Int16[])result)
                            {
                                str = str + ";" + intVar.ToString();
                            }
                            str = str.Remove(0, 1);
                            resultStrings.Add(str);
                        }
                        else if (result.ToString() == "System.UInt16[]")
                        {
                            string str = "";
                            foreach (UInt16 intVar in (UInt16[])result)
                            {
                                str = str + ";" + intVar.ToString();
                            }
                            str = str.Remove(0, 1);
                            resultStrings.Add(str);
                        }
                        else if (result.ToString() == "System.Int64[]")
                        {
                            string str = "";
                            foreach (Int64 intVar in (Int64[])result)
                            {
                                str = str + ";" + intVar.ToString();
                            }
                            str = str.Remove(0, 1);
                            resultStrings.Add(str);
                        }
                        else if (result.ToString() == "System.Single[]")
                        {
                            string str = "";
                            foreach (float intVar in (float[])result)
                            {
                                str = str + ";" + intVar.ToString();
                            }
                            str = str.Remove(0, 1);
                            resultStrings.Add(str);
                        }
                        else if (result.ToString() == "System.Double[]")
                        {
                            string str = "";
                            foreach (double intVar in (double[])result)
                            {
                                str = str + ";" + intVar.ToString();
                            }
                            str = str.Remove(0, 1);
                            resultStrings.Add(str);
                        }
                        else
                        {
                            resultStrings.Add(result.ToString());
                        }
                    }
                    else
                    {
                        resultStrings.Add("(null)");
                    }
                }
                return resultStrings;
            }
            catch (Exception e)
            {
                //handle Exception here
                throw e;
            }
        }

        /// <summary>Writes values to node Ids</summary>
        /// <param name="value">The values as strings</param>
        /// <param name="nodeIdString">The node Ids as strings</param>
        /// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
        public void WriteValues(List<String> values, List<String> nodeIdStrings)
        {
            //Create a collection of values to write
            WriteValueCollection valuesToWrite = new WriteValueCollection();
            //Create a collection for StatusCodes
            StatusCodeCollection result = new StatusCodeCollection();
            //Create a collection for DiagnosticInfos
            DiagnosticInfoCollection diagnostics = new DiagnosticInfoCollection();

            foreach (String str in nodeIdStrings)
            {
                //Create a nodeId
                NodeId nodeId = new NodeId(str);
                //Create a dataValue
                DataValue dataValue = new DataValue();
                //Read the dataValue
                try
                {
                    dataValue = session.ReadValue(nodeId);
                }
                catch (Exception e)
                {
                    //handle Exception here
                    throw e;
                }

                string test = dataValue.Value.GetType().Name;
                //Get the data type of the read dataValue
                //Handle Arrays here: TBD
                Variant variant = 0;
                try
                {
                    variant = new Variant(Convert.ChangeType(values[nodeIdStrings.IndexOf(str)], dataValue.Value.GetType()));
                }
                catch //no base data type
                {
                    //Handle different arrays types here: TBD
                    if (dataValue.Value.GetType().Name == "string[]")
                    {
                        string[] arrString = values[nodeIdStrings.IndexOf(str)].Split(';');
                        variant = new Variant(arrString);
                    }
                    else if (dataValue.Value.GetType().Name == "Byte[]")
                    {
                        string[] arrString = values[nodeIdStrings.IndexOf(str)].Split(';');
                        Byte[] arrInt = new Byte[arrString.Length];

                        for (int i = 0; i < arrString.Length; i++)
                        {
                            arrInt[i] = Convert.ToByte(arrString[i]);
                        }
                        variant = new Variant(arrInt);
                    }
                    else if (dataValue.Value.GetType().Name == "Int16[]")
                    {
                        string[] arrString = values[nodeIdStrings.IndexOf(str)].Split(';');
                        Int16[] arrInt = new Int16[arrString.Length];

                        for (int i = 0; i < arrString.Length; i++)
                        {
                            arrInt[i] = Convert.ToInt16(arrString[i]);
                        }
                        variant = new Variant(arrInt);
                    }
                }

                //Overwrite the dataValue with a new constructor using read dataType
                dataValue = new DataValue(variant);

                //Create a WriteValue using the NodeId, dataValue and attributeType
                WriteValue valueToWrite = new WriteValue();
                valueToWrite.Value = dataValue;
                valueToWrite.NodeId = nodeId;
                valueToWrite.AttributeId = Attributes.Value;

                //Add the dataValues to the collection
                valuesToWrite.Add(valueToWrite);
            }

            try
            {
                //Write the collection to the server
                session.Write(null, valuesToWrite, out result, out diagnostics);
                foreach (StatusCode code in result)
                {
                    if (code != 0)
                    {
                        Exception ex = new Exception(code.ToString());
                        throw ex;
                    }
                }
            }
            catch (Exception e)
            {
                //handle Exception here
                throw e;
            }
        }
        #endregion



        private static String GetTypeDictionary(String nodeIdString, Session theSessionToBrowseIn, out String parseString)
        {
            //Read the desired node first and chekc if it's a variable
            Node node = theSessionToBrowseIn.ReadNode(nodeIdString);
            if (node.NodeClass == NodeClass.Variable)
            {
                //Get the node id of node's data type
                VariableNode variableNode = (VariableNode)node.DataLock;
                NodeId nodeId = new NodeId(variableNode.DataType.Identifier, variableNode.DataType.NamespaceIndex);

                //Browse for HasEncoding
                ReferenceDescriptionCollection refDescCol;
                byte[] continuationPoint;
                theSessionToBrowseIn.Browse(null, null, nodeId, 0u, BrowseDirection.Forward, ReferenceTypeIds.HasEncoding, true, 0, out continuationPoint, out refDescCol);

                //Check For found reference
                if (refDescCol.Count == 0)
                {
                    Exception ex = new Exception("No data type to encode. Could be a build-in data type you want to read.");
                    throw ex;
                }

                //Check for HasEncoding reference with name "Default Binary"
                bool dataTypeFound = false;
                foreach (ReferenceDescription refDesc in refDescCol)
                {
                    if (refDesc.DisplayName.Text == "Default Binary")
                    {
                        nodeId = new NodeId(refDesc.NodeId.Identifier, refDesc.NodeId.NamespaceIndex);
                        dataTypeFound = true;
                    }
                    else if (dataTypeFound == false)
                    {
                        Exception ex = new Exception("No default binary data type found.");
                        throw ex;
                    }
                }

                //Browse for HasDescription
                refDescCol = null;
                theSessionToBrowseIn.Browse(null, null, nodeId, 0u, BrowseDirection.Forward, ReferenceTypeIds.HasDescription, true, 0, out continuationPoint, out refDescCol);

                //Check For found reference
                if (refDescCol.Count == 0)
                {
                    Exception ex = new Exception("No data type description found in address space.");
                    throw ex;
                }

                //Read from node id of the found description to get a value to parse for later on
                nodeId = new NodeId(refDescCol[0].NodeId.Identifier, refDescCol[0].NodeId.NamespaceIndex);
                DataValue resultValue = theSessionToBrowseIn.ReadValue(nodeId);
                parseString = resultValue.Value.ToString();

                //Browse for ComponentOf from last browsing result inversly
                refDescCol = null;
                theSessionToBrowseIn.Browse(null, null, nodeId, 0u, BrowseDirection.Inverse, ReferenceTypeIds.HasComponent, true, 0, out continuationPoint, out refDescCol);

                //Check if reference was found
                if (refDescCol.Count == 0)
                {
                    Exception ex = new Exception("Data type isn't a component of parent type in address space. Can't continue decoding.");
                    throw ex;
                }

                //Read from node id of the found HasCompoment reference to get a XML file (as HEX string) containing struct/UDT information

                nodeId = new NodeId(refDescCol[0].NodeId.Identifier, refDescCol[0].NodeId.NamespaceIndex);
                resultValue = theSessionToBrowseIn.ReadValue(nodeId);

                //Convert the HEX string to ASCII string
                String xmlString = ASCIIEncoding.ASCII.GetString((byte[])resultValue.Value);

                //Return the dictionary as ASCII string
                return xmlString;
            }
            {
                Exception ex = new Exception("No variable data type found");
                throw ex;
            }
        }

        private static List<object> ParseTypeDictionary(String xmlStringToParse, String stringToParserFor)
        {
            List<object> varList = new List<object>();

            //Remove last XML sign and create a XML document out of the dictionary string
            if (xmlStringToParse.EndsWith("\n"))
            {
                xmlStringToParse = xmlStringToParse.Remove(xmlStringToParse.Length - 1);
            }
            XmlDocument docToParse = new XmlDocument();
            docToParse.LoadXml(xmlStringToParse);

            //Get a XML node list of objectes named by "stringToParseFor"
            docToParse.GetElementsByTagName(stringToParserFor);
            XmlNodeList nodeList;
            nodeList = docToParse.GetElementsByTagName("opc:StructuredType");

            XmlNode foundNode = null;

            //search for the attribute name == "stringToParseFor"
            foreach (XmlNode node in nodeList)
            {
                if (node.Attributes["Name"].Value == stringToParserFor)
                {
                    foundNode = node;
                    break;
                }
            }

            //check if attribute name was found
            if (foundNode == null)
            {
                return null;
            }

            //get child nodes of parent node with attribute name == "stringToParseFor" and parse for var name and var type
            foreach (XmlNode node in foundNode.ChildNodes)
            {
                string[] dataReferenceStringArray = new string[2];

                dataReferenceStringArray[0] = node.Attributes["Name"].Value;
                dataReferenceStringArray[1] = node.Attributes["TypeName"].Value;
                if (!dataReferenceStringArray[1].Contains("tns:"))
                {
                    dataReferenceStringArray[1] = dataReferenceStringArray[1].Remove(0, 4);
                }
                else
                {
                    dataReferenceStringArray[1] = dataReferenceStringArray[1].Remove(0, 4);
                    dataReferenceStringArray[1] = dataReferenceStringArray[1].Insert(0, "STRUCT/UDT:");
                }


                varList.Add(dataReferenceStringArray);
            }

            //Check if result contains another struct/UDT inside and parse for var name and var type
            //Note: This check is consistent even if there are more structs/UDTs inside of structs/UDTs
            for (int count = 0; count < varList.Count; count++)
            {
                Object varObject = varList[count];
                //"tns:" indicates another strucht/UDT
                if (((string[])varObject)[1].Contains("STRUCT/UDT:"))
                {

                    XmlNode innerNode = null;
                    foreach (XmlNode anotherNode in nodeList)
                    {
                        if (anotherNode.Attributes["Name"].Value == ((string[])varObject)[1].Remove(0, 11))
                        {
                            innerNode = anotherNode;
                            break;
                        }
                    }

                    if (innerNode == null)
                    {
                        return null;
                    }

                    int i = 0;
                    foreach (XmlNode innerChildNode in innerNode.ChildNodes)
                    {
                        string[] innerDataReferenceStringArray = new string[2];
                        innerDataReferenceStringArray[0] = innerChildNode.Attributes["Name"].Value; ;
                        innerDataReferenceStringArray[1] = innerChildNode.Attributes["TypeName"].Value;
                        if (!innerDataReferenceStringArray[1].Contains("tns:"))
                        {
                            innerDataReferenceStringArray[1] = innerDataReferenceStringArray[1].Remove(0, 4);
                        }
                        else
                        {
                            innerDataReferenceStringArray[1] = innerDataReferenceStringArray[1].Remove(0, 4);
                            innerDataReferenceStringArray[1] = innerDataReferenceStringArray[1].Insert(0, "STRUCT/UDT:");
                        }

                        varList.Insert(varList.IndexOf(varObject) + 1 + i, innerDataReferenceStringArray);
                        i += 1;

                        if (i == innerNode.ChildNodes.Count)
                        {
                            string[] innerDataReferenceStringArrayEnd = new string[2];
                            innerDataReferenceStringArrayEnd[0] = ((string[])varObject)[0];
                            innerDataReferenceStringArrayEnd[1] = "END_STRUCT/UDT";
                            varList.Insert(varList.IndexOf(varObject) + 1 + i, innerDataReferenceStringArrayEnd);
                        }
                    }
                }
            }
            return varList;
        }


        private static List<string[]> ParseDataToTagsFromDictionary(List<Object> varList, byte[] byteResult)
        {
            //Define result list to return var name, var value and var data type
            List<string[]> resultStringList = new List<string[]>();

            //Byte decoding index
            int index = 0;

            //Int used to decode arrays
            Int32 arraylength = 0;

            //Start decoding for opc data types
            foreach (object val in varList)
            {
                string[] dataReferenceStringArray = new string[3];
                dataReferenceStringArray[0] = ((string[])val)[0];

                if (((string[])val)[1] == "Boolean" && !(arraylength > 0))
                {
                    dataReferenceStringArray[1] = BitConverter.ToBoolean(byteResult, index).ToString();
                    index += 1;
                }
                else if (((string[])val)[1] == "Int16" && !(arraylength > 0))
                {
                    dataReferenceStringArray[1] = BitConverter.ToInt16(byteResult, index).ToString();
                    index += 2;
                }
                else if (((string[])val)[1] == "Int16" && arraylength > 0)
                {
                    Int16[] tempArray = new Int16[arraylength];
                    for (int i = 0; i < arraylength; i++)
                    {
                        tempArray[i] = BitConverter.ToInt16(byteResult, index);
                        index += 2;
                    }
                    dataReferenceStringArray[1] = String.Join(";", tempArray);
                    dataReferenceStringArray[1] = String.Concat(dataReferenceStringArray[1], ";");
                    arraylength = 0;
                }
                else if (((string[])val)[1] == "UInt16" && arraylength > 0)
                {
                    UInt16[] tempArray = new UInt16[arraylength];
                    for (int i = 0; i < arraylength; i++)
                    {
                        tempArray[i] = BitConverter.ToUInt16(byteResult, index);
                        index += 2;
                    }
                    dataReferenceStringArray[1] = String.Join(";", tempArray);
                    dataReferenceStringArray[1] = String.Concat(dataReferenceStringArray[1], ";");
                    arraylength = 0;
                }
                else if (((string[])val)[1] == "Int32" && !(arraylength > 0) && !((string[])val)[0].Contains("_Size"))
                {
                    dataReferenceStringArray[1] = BitConverter.ToInt32(byteResult, index).ToString();
                    index += 4;
                }
                else if (((string[])val)[1] == "Int32" && arraylength > 0)
                {
                    Int32[] tempArray = new Int32[arraylength];
                    for (int i = 0; i < arraylength; i++)
                    {
                        tempArray[i] = BitConverter.ToInt32(byteResult, index);
                        index += 4;
                    }
                    dataReferenceStringArray[1] = String.Join(";", tempArray);
                    dataReferenceStringArray[1] = String.Concat(dataReferenceStringArray[1], ";");
                    arraylength = 0;
                }
                else if (((string[])val)[1] == "UInt32" && arraylength > 0)
                {
                    UInt32[] tempArray = new UInt32[arraylength];
                    for (int i = 0; i < arraylength; i++)
                    {
                        tempArray[i] = BitConverter.ToUInt32(byteResult, index);
                        index += 4;
                    }
                    dataReferenceStringArray[1] = String.Join(";", tempArray);
                    dataReferenceStringArray[1] = String.Concat(dataReferenceStringArray[1], ";");
                    arraylength = 0;
                }
                else if (((string[])val)[1] == "Int64" && !(arraylength > 0))
                {
                    dataReferenceStringArray[1] = BitConverter.ToInt64(byteResult, index).ToString();
                    index += 8;
                }
                else if (((string[])val)[1] == "UInt64" && arraylength > 0)
                {
                    UInt64[] tempArray = new UInt64[arraylength];
                    for (int i = 0; i < arraylength; i++)
                    {
                        tempArray[i] = BitConverter.ToUInt64(byteResult, index);
                        index += 8;
                    }
                    dataReferenceStringArray[1] = String.Join(";", tempArray);
                    dataReferenceStringArray[1] = String.Concat(dataReferenceStringArray[1], ";");
                    arraylength = 0;
                }
                else if (((string[])val)[1] == "Float" && !(arraylength > 0))
                {
                    dataReferenceStringArray[1] = BitConverter.ToSingle(byteResult, index).ToString();
                    index += 4;
                }
                else if (((string[])val)[1] == "Float" && arraylength > 0)
                {
                    Single[] tempArray = new Single[arraylength];
                    for (int i = 0; i < arraylength; i++)
                    {
                        tempArray[i] = BitConverter.ToSingle(byteResult, index);
                        index += 4;
                    }
                    dataReferenceStringArray[1] = String.Join(";", tempArray);
                    dataReferenceStringArray[1] = String.Concat(dataReferenceStringArray[1], ";");
                    arraylength = 0;
                }
                else if (((string[])val)[1] == "Double" && !(arraylength > 0))
                {
                    dataReferenceStringArray[1] = BitConverter.ToDouble(byteResult, index).ToString();
                    index += 8;
                }
                else if (((string[])val)[1] == "Double" && arraylength > 0)
                {
                    Double[] tempArray = new Double[arraylength];
                    for (int i = 0; i < arraylength; i++)
                    {
                        tempArray[i] = BitConverter.ToDouble(byteResult, index);
                        index += 8;
                    }
                    dataReferenceStringArray[1] = String.Join(";", tempArray);
                    dataReferenceStringArray[1] = String.Concat(dataReferenceStringArray[1], ";");
                    arraylength = 0;
                }
                else if (((string[])val)[1] == "String" && !(arraylength > 0))
                {
                    Int32 stringlength = BitConverter.ToInt32(byteResult, index);
                    index += 4;
                    if (stringlength > 0)
                    {
                        dataReferenceStringArray[1] = Encoding.UTF8.GetString(byteResult, index, stringlength);
                        index += stringlength;
                    }
                    else
                    {
                        dataReferenceStringArray[1] = "";
                    }
                }
                else if (((string[])val)[1] == "CharArray")
                {
                    Int32 stringlength = BitConverter.ToInt32(byteResult, index);
                    index += 4;
                    if (stringlength > 0)
                    {
                        dataReferenceStringArray[1] = Encoding.UTF8.GetString(byteResult, index, stringlength);
                        index += stringlength;
                    }
                    else
                    {
                        dataReferenceStringArray[1] = "";
                    }
                }
                else if (((string[])val)[1] == "UInt16" && !(arraylength > 0))
                {
                    dataReferenceStringArray[1] = BitConverter.ToUInt16(byteResult, index).ToString();
                    index += 2;
                }
                else if (((string[])val)[1] == "UInt32" && !(arraylength > 0))
                {
                    dataReferenceStringArray[1] = BitConverter.ToUInt32(byteResult, index).ToString();
                    index += 4;
                }
                else if (((string[])val)[1] == "Int64" && arraylength > 0)
                {
                    Int64[] tempArray = new Int64[arraylength];
                    for (int i = 0; i < arraylength; i++)
                    {
                        tempArray[i] = BitConverter.ToInt64(byteResult, index);
                        index += 8;
                    }
                    dataReferenceStringArray[1] = String.Join(";", tempArray);
                    dataReferenceStringArray[1] = String.Concat(dataReferenceStringArray[1], ";");
                    arraylength = 0;
                }
                else if (((string[])val)[1] == "UInt64" && !(arraylength > 0))
                {
                    dataReferenceStringArray[1] = BitConverter.ToUInt64(byteResult, index).ToString();
                    index += 8;
                }
                else if (((string[])val)[1] == "Byte" && !(arraylength > 0))
                {
                    dataReferenceStringArray[1] = byteResult[index].ToString();
                    index += 1;
                }
                else if (((string[])val)[1] == "Int32" && ((string[])val)[0].Contains("_Size"))
                {
                    arraylength = BitConverter.ToInt32(byteResult, index);
                    dataReferenceStringArray[1] = arraylength.ToString();
                    index += 4;
                }
                else if (((string[])val)[1] == "Byte" && arraylength > 0)
                {
                    Int32[] tempArray = new Int32[arraylength];
                    for (int i = 0; i < arraylength; i++)
                    {
                        tempArray[i] = byteResult[index];
                        index += 1;
                    }
                    dataReferenceStringArray[1] = String.Join(";", tempArray);
                    dataReferenceStringArray[1] = String.Concat(dataReferenceStringArray[1], ";");
                    arraylength = 0;
                }
                else if (((string[])val)[1] == "String" && arraylength > 0)
                {
                    for (int i = 0; i < arraylength; i++)
                    {
                        Int32 stringlength = BitConverter.ToInt32(byteResult, index);
                        index += 4;
                        if (stringlength > 0)
                        {

                            dataReferenceStringArray[1] = String.Concat(dataReferenceStringArray[1], Encoding.UTF8.GetString(byteResult, index, stringlength));
                            dataReferenceStringArray[1] = String.Concat(dataReferenceStringArray[1], ";");
                            index += stringlength;
                        }
                        else
                        {
                            dataReferenceStringArray[1] = dataReferenceStringArray[1] + ";";
                        }
                    }
                    arraylength = 0;
                }
                else if (((string[])val)[1].Contains("STRUCT/UDT") && !(arraylength > 0))
                {
                    dataReferenceStringArray[1] = "..";
                }
                else
                {
                    Exception e = new Exception("Data type is too complex to be parsed." + System.Environment.NewLine + "Please avoid array of UDT/Struct inside UDT/Struct");
                    throw e;
                }
                dataReferenceStringArray[2] = ((string[])val)[1];
                resultStringList.Add(dataReferenceStringArray);
            }
            return resultStringList;
        }

        private static Int64 GetLengthOfDataToWrite(List<string[]> dataToWrite)
        {
            Int64 length = 0;
            Int32 arraySize = 0;
            foreach (string[] val in dataToWrite)
            {
                if (arraySize > 0)
                {
                    arraySize = 0;
                    continue;
                }
                if (val[2] == "Boolean")
                {
                    length += 1;
                }
                else if (val[2] == "Int16")
                {
                    length += 2;
                }
                else if (val[2] == "Float")
                {
                    length += 4;
                }
                else if (val[2] == "Double")
                {
                    length += 8;
                }
                else if (val[2] == "UInt16")
                {
                    length += 2;
                }
                else if (val[2] == "Int32" && !(val[0].Contains("_Size")))
                {
                    length += 4;
                }
                else if (val[2] == "UInt32")
                {
                    length += 4;
                }
                else if (val[2] == "Int64")
                {
                    length += 8;
                }
                else if (val[2] == "UInt64")
                {
                    length += 8;
                }
                else if (val[2] == "Byte")
                {
                    length += 1;
                }
                else if (val[2] == "String")
                {
                    length += val[1].Length + 4;
                }
                else if (val[2] == "CharArray")
                {
                    length += val[1].Length + 4;
                }
                else if (val[2] == "Int32" && val[0].Contains("_Size"))
                {
                    arraySize = Convert.ToInt32(val[1]);

                    if (((string[])dataToWrite[dataToWrite.IndexOf(val) + 1])[2].Contains("String"))
                    {
                        string[] tempStringArr = new string[arraySize];
                        tempStringArr = ((string[])dataToWrite[dataToWrite.IndexOf(val) + 1])[1].Split(';');
                        for (int ii = 0; ii < arraySize; ii++)
                        {
                            length += (4 + tempStringArr[ii].Length);
                        }
                        length += 4;
                    }
                    else if (((string[])dataToWrite[dataToWrite.IndexOf(val) + 1])[2].Contains("Int16"))
                    {
                        length += (4 + (arraySize * 2));
                    }
                    else if (((string[])dataToWrite[dataToWrite.IndexOf(val) + 1])[2].Contains("Int32"))
                    {
                        length += (4 + (arraySize * 4));
                    }
                    else if (((string[])dataToWrite[dataToWrite.IndexOf(val) + 1])[2].Contains("Int64"))
                    {
                        length += (4 + (arraySize * 8));
                    }
                    else if (((string[])dataToWrite[dataToWrite.IndexOf(val) + 1])[2].Contains("Boolean"))
                    {
                        length += (4 + arraySize);
                    }
                    else if (((string[])dataToWrite[dataToWrite.IndexOf(val) + 1])[2].Contains("Byte"))
                    {
                        length += (4 + arraySize);
                    }
                    else if (((string[])dataToWrite[dataToWrite.IndexOf(val) + 1])[2].Contains("Float"))
                    {
                        length += (4 + (arraySize * 4));
                    }
                    else if (((string[])dataToWrite[dataToWrite.IndexOf(val) + 1])[2].Contains("Int32"))
                    {
                        length += (4 + (arraySize * 8));
                    }
                }
                else if (val[2].Contains("STRUCT/UDT"))
                {
                    ;
                }
                else
                {
                    Exception e = new Exception("Unknow data type. Can't determine length of data");
                    throw e;
                }
            }
            return length;
        }


        private static byte[] ParseDataToByteArray(List<string[]> dataToWrite, Int64 dataLength)
        {
            byte[] bytesToWrite = new byte[dataLength];
            Int32 convertIndex = 0;
            Int32 arraySize = 0;
            foreach (string[] val in dataToWrite)
            {
                if (val[2] == "Boolean" && !(arraySize > 0))
                {
                    Boolean tempBool = Convert.ToBoolean(val[1]);
                    bytesToWrite[convertIndex] = Convert.ToByte(tempBool);
                    convertIndex += 1;
                }
                else if (val[2] == "Int16" && !(arraySize > 0))
                {
                    Array.Copy(BitConverter.GetBytes(Convert.ToInt16(val[1])), 0, bytesToWrite, convertIndex, 2);
                    convertIndex += 2;
                }
                else if (val[2] == "UInt16" && !(arraySize > 0))
                {
                    Array.Copy(BitConverter.GetBytes(Convert.ToUInt16(val[1])), 0, bytesToWrite, convertIndex, 2);
                    convertIndex += 2;
                }
                else if (val[2] == "Float" && !(arraySize > 0))
                {
                    Array.Copy(BitConverter.GetBytes(Convert.ToSingle(val[1])), 0, bytesToWrite, convertIndex, 4);
                    convertIndex += 4;
                }
                else if (val[2] == "Double" && !(arraySize > 0))
                {
                    Array.Copy(BitConverter.GetBytes(Convert.ToDouble(val[1])), 0, bytesToWrite, convertIndex, 8);
                    convertIndex += 8;
                }
                else if (val[2] == "Int32" && !(val[0].Contains("_Size")) && !(arraySize > 0))
                {
                    Array.Copy(BitConverter.GetBytes(Convert.ToUInt32(val[1])), 0, bytesToWrite, convertIndex, 4);
                    convertIndex += 4;
                }
                else if (val[2] == "UInt32" && !(arraySize > 0))
                {
                    Array.Copy(BitConverter.GetBytes(Convert.ToUInt32(val[1])), 0, bytesToWrite, convertIndex, 4);
                    convertIndex += 4;
                }
                else if (val[2] == "Int64" && !(arraySize > 0))
                {
                    Array.Copy(BitConverter.GetBytes(Convert.ToInt64(val[1])), 0, bytesToWrite, convertIndex, 8);
                    convertIndex += 8;
                }
                else if (val[2] == "UInt64" && !(arraySize > 0))
                {
                    Array.Copy(BitConverter.GetBytes(Convert.ToUInt64(val[1])), 0, bytesToWrite, convertIndex, 8);
                    convertIndex += 8;
                }
                else if (val[2] == "Byte" && !(arraySize > 0))
                {
                    bytesToWrite[convertIndex] = Convert.ToByte(val[1]);
                    convertIndex += 1;
                }
                else if (val[2] == "String" && !(arraySize > 0))
                {
                    Array.Copy(BitConverter.GetBytes(val[1].Length), 0, bytesToWrite, convertIndex, 4);
                    convertIndex += 4;
                    foreach (Char c in val[1])
                    {
                        bytesToWrite[convertIndex] = Convert.ToByte(c);
                        convertIndex += 1;
                    }
                }
                else if (val[2] == "CharArray")
                {
                    Array.Copy(BitConverter.GetBytes(val[1].Length), 0, bytesToWrite, convertIndex, 4);
                    convertIndex += 4;
                    foreach (Char c in val[1])
                    {
                        bytesToWrite[convertIndex] = Convert.ToByte(c);
                        convertIndex += 1;
                    }
                }
                else if (val[2] == "Int32" && val[0].Contains("_Size"))
                {
                    Array.Copy(BitConverter.GetBytes(Convert.ToUInt32(val[1])), 0, bytesToWrite, convertIndex, 4);
                    arraySize = Convert.ToInt32(val[1]);
                    convertIndex += 4;
                }
                else if (val[2] == "Byte" && arraySize > 0)
                {
                    String tempString = "";
                    foreach (Char c in val[1])
                    {
                        if (c != ';')
                        {
                            tempString = String.Concat(tempString, c);
                        }
                        else
                        {
                            bytesToWrite[convertIndex] = Convert.ToByte(tempString);
                            convertIndex += 1;
                            tempString = "";
                        }
                    }
                    arraySize = 0;
                }
                else if (val[2] == "Int16" && arraySize > 0)
                {
                    String tempString = "";
                    foreach (Char c in val[1])
                    {
                        if (c != ';')
                        {
                            tempString = String.Concat(tempString, c);
                        }
                        else
                        {
                            Array.Copy(BitConverter.GetBytes(Convert.ToInt16(tempString)), 0, bytesToWrite, convertIndex, 2);
                            convertIndex += 2;
                            tempString = "";
                        }
                    }
                    arraySize = 0;
                }
                else if (val[2] == "UInt16" && arraySize > 0)
                {
                    String tempString = "";
                    foreach (Char c in val[1])
                    {
                        if (c != ';')
                        {
                            tempString = String.Concat(tempString, c);
                        }
                        else
                        {
                            Array.Copy(BitConverter.GetBytes(Convert.ToUInt16(tempString)), 0, bytesToWrite, convertIndex, 2);
                            convertIndex += 2;
                            tempString = "";
                        }
                    }
                    arraySize = 0;
                }
                else if (val[2] == "Int32" && arraySize > 0)
                {
                    String tempString = "";
                    foreach (Char c in val[1])
                    {
                        if (c != ';')
                        {
                            tempString = String.Concat(tempString, c);
                        }
                        else
                        {
                            Array.Copy(BitConverter.GetBytes(Convert.ToInt32(tempString)), 0, bytesToWrite, convertIndex, 4);
                            convertIndex += 4;
                            tempString = "";
                        }
                    }
                    arraySize = 0;
                }
                else if (val[2] == "UInt32" && arraySize > 0)
                {
                    String tempString = "";
                    foreach (Char c in val[1])
                    {
                        if (c != ';')
                        {
                            tempString = String.Concat(tempString, c);
                        }
                        else
                        {
                            Array.Copy(BitConverter.GetBytes(Convert.ToUInt32(tempString)), 0, bytesToWrite, convertIndex, 4);
                            convertIndex += 4;
                            tempString = "";
                        }
                    }
                    arraySize = 0;
                }
                else if (val[2] == "Int64" && arraySize > 0)
                {
                    String tempString = "";
                    foreach (Char c in val[1])
                    {
                        if (c != ';')
                        {
                            tempString = String.Concat(tempString, c);
                        }
                        else
                        {
                            Array.Copy(BitConverter.GetBytes(Convert.ToInt64(tempString)), 0, bytesToWrite, convertIndex, 8);
                            convertIndex += 8;
                            tempString = "";
                        }
                    }
                    arraySize = 0;
                }
                else if (val[2] == "UInt64" && arraySize > 0)
                {
                    String tempString = "";
                    foreach (Char c in val[1])
                    {
                        if (c != ';')
                        {
                            tempString = String.Concat(tempString, c);
                        }
                        else
                        {
                            Array.Copy(BitConverter.GetBytes(Convert.ToUInt64(tempString)), 0, bytesToWrite, convertIndex, 8);
                            convertIndex += 8;
                            tempString = "";
                        }
                    }
                    arraySize = 0;
                }
                else if (val[2] == "Float" && arraySize > 0)
                {
                    String tempString = "";
                    foreach (Char c in val[1])
                    {
                        if (c != ';')
                        {
                            tempString = String.Concat(tempString, c);
                        }
                        else
                        {
                            Array.Copy(BitConverter.GetBytes(Convert.ToSingle(tempString)), 0, bytesToWrite, convertIndex, 4);
                            convertIndex += 4;
                            tempString = "";
                        }
                    }
                    arraySize = 0;
                }
                else if (val[2] == "String" && arraySize > 0)
                {
                    string[] tempStringArr = new string[arraySize];
                    tempStringArr = val[1].Split(';');
                    for (int ii = 0; ii < arraySize; ii++)
                    {
                        Array.Copy(BitConverter.GetBytes(tempStringArr[ii].Length), 0, bytesToWrite, convertIndex, 4);
                        convertIndex += 4;
                        foreach (Char c in tempStringArr[ii])
                        {
                            bytesToWrite[convertIndex] = Convert.ToByte(c);
                            convertIndex += 1;
                        }
                    }
                    arraySize = 0;
                }
                else if (val[2].Contains("STRUCT/UDT"))
                {
                    ;
                }
                else
                {
                    Exception e = new Exception("Can't covert " + val[0] + ".");
                    throw e;
                }
            }
            return bytesToWrite;
        }




        #region Read/Write Struct/UDT   读写 Struct/UDT
        /// <summary>Reads a struct or UDT by node Id</summary>
        /// <param name="nodeIdString">The node Id as strings</param>
        /// <returns>The read struct/UDT elements as a list of string[3]; string[0] = tag name, string[1] = value, string[2] = opc data type</returns>
        /// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
        public List<string[]> ReadStructUdt(String nodeIdString)
        {
            //Define result list to return var name and var value
            List<string[]> resultStringList = new List<string[]>();

            //Get the type dictionary of desired struct/UDT and the name of desired var to parse
            String parseString;
            String xmlString = GetTypeDictionary(nodeIdString, session, out parseString);

            //Parse xmlString to create objects of the struct/UDT containing var name and var data type
            List<object> varList = new List<object>();
            varList = ParseTypeDictionary(xmlString, parseString);

            //Read the struct
            List<NodeId> nodeIds = new List<NodeId>();
            NodeId myNode = new NodeId(nodeIdString);
            nodeIds.Add(myNode);
            List<ServiceResult> serviceResults = new List<ServiceResult>();
            List<Object> values = new List<object>();
            List<Type> types = new List<Type>();
            types.Add(null);
            try
            {
                session.ReadValues(nodeIds, types, out values, out serviceResults);
            }
            catch (Exception e)
            {
                //Handle Exception here
                throw e;
            }

            //Check result codes
            foreach (ServiceResult svResult in serviceResults)
            {
                if (svResult.ToString() != "Good")
                {
                    Exception e = new Exception(svResult.ToString());
                    throw e;
                }
            }

            //Create an empty byte-array to store ExtensionObject.Body (containing the whole binary data of the desired strucht/UDT) into
            byte[] readBinaryData = null;
            foreach (object val in values)
            {
                //Cast object to ExtensionObject
                ExtensionObject encodeable = val as ExtensionObject;

                //If encodable == null there might be an array
                if (encodeable == null)
                {
                    ExtensionObject[] exObjArr = val as ExtensionObject[];

                    for (int i = 0; i < exObjArr.Length; i++)
                    {
                        encodeable = exObjArr[i];

                        //Write the body of the ExtensionObject into the byte-array
                        readBinaryData = (byte[])encodeable.Body;

                        //Check for data types and parse byte array 
                        resultStringList.Add(new string[] { "[" + (i + 1).ToString() + "]", "..", "ARRAY[" + exObjArr.Length.ToString() + "] OF STRUCT/UDT" });
                        resultStringList.AddRange(ParseDataToTagsFromDictionary(varList, readBinaryData));
                    }
                }
                else
                {
                    //Write the body(=data) of the ExtensionObject into the byte-array
                    readBinaryData = (byte[])encodeable.Body;

                    //Check for data types and parse byte array 
                    resultStringList.AddRange(ParseDataToTagsFromDictionary(varList, readBinaryData));
                }
            }

            //return result as List<string[]> (string[0]=tag name; string[1]=tag value; string[2]=tag data type
            return resultStringList;
        }

        /// <summary>Writes data to a struct or UDT by node Id</summary>
        /// <param name="nodeIdString">The node Id as strings</param>
        /// <param name="dataToWrite">The data to write as string[3]; string[0] = tag name, string[1] = value, string[2] = opc data type</param>
        /// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
        public void WriteStructUdt(String nodeIdString, List<string[]> dataToWrite)
        {
            //Create a NodeId from the NodeIdString
            NodeId nodeId = new NodeId(nodeIdString);

            //Creat a WriteValueColelction
            WriteValueCollection valuesToWrite = new WriteValueCollection();

            //Create a WriteValue
            WriteValue writevalue = new WriteValue();

            //Create a StatusCodeCollection
            StatusCodeCollection results = new StatusCodeCollection();

            //Create a DiagnosticInfoCollection
            DiagnosticInfoCollection diag = new DiagnosticInfoCollection();

            //Create an ExtensionObject from the Structure given to this function
            ExtensionObject writeExtObj = new ExtensionObject();

            DataValue dataValue = null;

            //This is for regular Struct/UDT            
            if (!dataToWrite[0][2].Contains("OF STRUCT/UDT"))
            {
                //Determine lentgh of byte array needed to contain all data
                Int64 length = GetLengthOfDataToWrite(dataToWrite);

                //Create a byte array
                byte[] bytesToWrite;

                //Parse dataToWrite to the byte array
                bytesToWrite = ParseDataToByteArray(dataToWrite, length);

                //Copy data to extension object body
                writeExtObj.Body = bytesToWrite;

                //Turn the created ExtensionObject into a DataValue
                dataValue = new DataValue(writeExtObj);
            }
            //This is for array of Struct/UDT
            else
            {
                //Get array dimension
                int startDim = dataToWrite[0][2].IndexOf("[") + 1;
                int endDim = dataToWrite[0][2].IndexOf("]");

                string arrDimString = dataToWrite[0][2].Substring(startDim, endDim - startDim);
                Int32 arrDim = Convert.ToInt32(arrDimString);

                //Declar array of extension objects
                ExtensionObject[] exObjArr = new ExtensionObject[arrDim];

                //Split string list containing data to write 
                int index = 0;
                for (int i = 0; i < arrDim; i++)
                {
                    //Create temporary string list
                    List<string[]> splitData = new List<string[]>();

                    //Search for index pos
                    for (int j = index; j < dataToWrite.Count; j++)
                    {
                        if (!dataToWrite[j][0].Contains("[" + (i + 2).ToString() + "]"))
                        {
                            splitData.Add(dataToWrite[j]);
                        }
                        else
                        {
                            index = j + 1;
                            break;
                        }
                    }

                    //Determine lentgh of byte array needed to contain all data
                    Int64 length = GetLengthOfDataToWrite(splitData);

                    //Create a byte array
                    byte[] bytesToWrite;

                    //Parse dataToWrite to the byte array
                    bytesToWrite = ParseDataToByteArray(splitData, length);

                    //Copy data to extension object body
                    exObjArr[i] = new ExtensionObject();
                    exObjArr[i].Body = (Object)bytesToWrite;
                }

                //Turn the created ExtensionObject into a DataValue
                dataValue = new DataValue(exObjArr);
            }

            //Setup for the WriteValue
            writevalue.NodeId = nodeId;
            writevalue.Value = dataValue;
            writevalue.AttributeId = Attributes.Value;
            //Add the created value to the collection
            valuesToWrite.Add(writevalue);

            try
            {
                session.Write(null, valuesToWrite, out results, out diag);
            }
            catch (Exception e)
            {
                //Handle Exception here
                throw e;
            }

            //Check result codes
            foreach (StatusCode result in results)
            {
                if (result.ToString() != "Good")
                {
                    Exception e = new Exception(result.ToString());
                    throw e;
                }
            }
        }
        #endregion
















        //测试读写

        //读
        public object readTag(string tag)
        {
            if (session == null) return null;
            ReferenceDescription valueRef = GetReference(tag);   //获取PLC的Tag对应的ID等信息
            if (valueRef == null) return null;  //未能找到此节点
            ReadValueId nodeToRead = new ReadValueId();
            nodeToRead.NodeId = (NodeId)valueRef.NodeId;  //{ns=4;i=18} 节点ID
            nodeToRead.AttributeId = Attributes.Value;

            ReadValueIdCollection nodesToRead = new ReadValueIdCollection();
            nodesToRead.Add(nodeToRead);

            DataValueCollection readResults = null;
            DiagnosticInfoCollection readDiagnosticInfos = null;

            try
            {
                ResponseHeader readHeader = session.Read(null, 0, TimestampsToReturn.Neither, nodesToRead,
                    out readResults, out readDiagnosticInfos);
            }
            catch (Exception e)
            {
                return null;
            }
            ClientBase.ValidateResponse(readResults, nodesToRead);
            ClientBase.ValidateDiagnosticInfos(readDiagnosticInfos, nodesToRead);

            return readResults[0];  //读取到的变量值
        }
        //写
        public object writeTag(string tag, string typeString, object value) //根据已知的数据类型，写入
        {
            if (session == null) return null;
            ReferenceDescription valueRef = GetReference(tag);   //获取PLC的Tag对应的ID等信息
            if (valueRef == null) return null;  //未能找到此节点
            WriteValue valueToWrite = new WriteValue();
            valueToWrite.NodeId = (NodeId)valueRef.NodeId;
            valueToWrite.AttributeId = Attributes.Value;

            valueToWrite.Value.Value = ChangeType(typeString, value);

            valueToWrite.Value.StatusCode = StatusCodes.Good;
            valueToWrite.Value.ServerTimestamp = DateTime.MinValue;
            valueToWrite.Value.SourceTimestamp = DateTime.MinValue;

            WriteValueCollection valuesToWrite = new WriteValueCollection();
            valuesToWrite.Add(valueToWrite);

            StatusCodeCollection writeResults = null;
            DiagnosticInfoCollection writeDiagnosticInfos = null;

            try
            {
                ResponseHeader writeHeader = session.Write(null, valuesToWrite, out writeResults, out writeDiagnosticInfos);
            }
            catch (Exception e)
            {
                return null;
            }

            ClientBase.ValidateResponse(writeResults, valuesToWrite);
            ClientBase.ValidateDiagnosticInfos(writeDiagnosticInfos, valuesToWrite);

            return writeResults[0];
        }


        public object writeTag(string tag, object value) // 先读一遍，获取类型再写入，不推荐（速度慢）
        {
            if (session == null) return null;
            ReferenceDescription valueRef = GetReference(tag);   //获取PLC的Tag对应的ID等信息
            if (valueRef == null) return null;  //未能找到此节点
            WriteValue valueToWrite = new WriteValue();
            valueToWrite.NodeId = (NodeId)valueRef.NodeId;
            valueToWrite.AttributeId = Attributes.Value;

            var currentValue = readTag(tag) as DataValue;
            valueToWrite.Value.Value = ChangeType(currentValue, value);

            valueToWrite.Value.StatusCode = StatusCodes.Good;
            valueToWrite.Value.ServerTimestamp = DateTime.MinValue;
            valueToWrite.Value.SourceTimestamp = DateTime.MinValue;

            WriteValueCollection valuesToWrite = new WriteValueCollection();
            valuesToWrite.Add(valueToWrite);

            StatusCodeCollection writeResults = null;
            DiagnosticInfoCollection writeDiagnosticInfos = null;

            try
            {
                ResponseHeader writeHeader = session.Write(null, valuesToWrite, out writeResults, out writeDiagnosticInfos);
            }
            catch (Exception e)
            {
                return null;
            }


            ClientBase.ValidateResponse(writeResults, valuesToWrite);
            ClientBase.ValidateDiagnosticInfos(writeDiagnosticInfos, valuesToWrite);

            return writeResults[0];
        }













        //---------------------------------以下是测试用代码


        public Client()
        {
            InitializeComponent();

            CheckForIllegalCrossThreadCalls = false; //跨线程更新界面需要

            browseControl1.SelectionChanged += new BrowseControl.SelectionChangedEventHandler(browserControl_SelectionChanged);

            var a = new plcComm();
        }

        private void browserControl_SelectionChanged(TreeNode node)
        {
            // Read all the attributes of the selected tree node.
            //attributeListControl.ReadAttributes(node);
        }

        private void setCurrentSessionMsg(string msg)
        {
            currentSessionMessage = msg;

            lbmsg.Text = currentSessionMessage;

        }
        private void Client_FormClosing(object sender, FormClosingEventArgs e)
        {
            if(session !=null) session.Close(); // 停止与OPC服务器的通讯
        }

        //加载DGV监控列表用到
        private int plcItemId = 0;
        private BindingList<plcItem> plcItemList = new BindingList<plcItem>();
        public class plcItem
        {
            public int ID { get; set; }
            public string tagName { get; set; }
            public string nodeID { get; set; }
            public string value { get; set; }
            public DateTime updateTime { get; set; }
            public string tag { get; set; }
        }
        private void PrintRecursive(TreeNode treeNode, string addr)
        {

            if (treeNode.Text == "dummy") return;

            if (treeNode.Nodes.Count == 0) //终点节点，要加载到列表的部分
            {

                string[] tag1 = (addr + "." + treeNode.Text).Split(new string[] { "." }, System.StringSplitOptions.RemoveEmptyEntries);

                for (int i = 0; i < tag1.Length - 1; i++)
                {
                    tag1[i] = tag1[i].Split(new string[] { "-NodeId-" }, System.StringSplitOptions.RemoveEmptyEntries)[0];
                }

                string[] tag2 = (tag1[tag1.Length - 1]).Split(new string[] { "-NodeId-" }, System.StringSplitOptions.RemoveEmptyEntries);

                tag1 = tag1.Take(tag1.Count() - 1).ToArray();

                string tag = string.Join(".", tag1) + "." + tag2[0];

                string InterFace = tbInterFaceName.Text.Trim();

                if (InterFace != "") //筛选此接口变量
                {
                    if (tag.Contains(InterFace))
                    {
                        //sql.exec($"INSERT INTO PLC ( plcid, plcip, nodeid, tag, name ) VALUES ( {plcItemId}, '{tbServer.Text}', '{tag2[1]}', '{tag}', '{tag2[0]}' )");

                        plcItemList.Add(new plcItem()
                        {
                            ID = plcItemId++,
                            tagName = tag2[0],
                            nodeID = tag2[1],
                            tag = tag,
                            updateTime = DateTime.Now
                        });
                    }
                    


                }
                else
                {
                    //plcItemList.Add(new plcItem() { ID = plcItemId++, tagName = tag2[0], nodeID = tag2[1], tag = tag}); 
                }


                return;
            }

            // Visit each node recursively.  
            if(treeNode.Nodes !=null)
            foreach (TreeNode tn in treeNode.Nodes)
            {
                PrintRecursive(tn, addr + "." + treeNode.Text);
            }


        }


        private void button6_Click(object sender, EventArgs e)
        {

            TreeView treeView = browseControl1.BrowseTree;

            plcItemId = 0;

            while (plcItemList.Count > 0)
            {
                plcItemList.RemoveAt(0);
            }//清空

            foreach (TreeNode n in treeView.Nodes)
            {

                PrintRecursive(n, "");
            }

              dataGridView1.DataSource = plcItemList;

        }


        private void button1_Click(object sender, EventArgs e)
        {




            tbTag.Enabled = false;
            lbread.Text = "";
            //var a1 = readTag("ServerInterfaces.服务器接口_1.电机(1)")?.ToString();
            //var a2 = readTag("ServerInterfaces.服务器接口_1.启动(1)")?.ToString();
            //var a3 = readTag("ServerInterfaces.服务器接口_1.数据块_上位机交互.启动");

            var res = readTag(tbTag.Text);
            tbTag.Enabled = true;

            if (res == null) { lbread.Text = "读取失败"; return; }

            lbread.Text=res.ToString();


            return;

            Node node = null;
            List<string> result = null;
            try
            {
                node = ReadNode(tbNodeID.Text);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }

            List<String> nodeIdStrings = new List<string>();
            nodeIdStrings.Add(tbNodeID.Text);

            try
            {
                result = ReadValues(nodeIdStrings);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }

        }

        private void button2_Click(object sender, EventArgs e)
        {
            tbTag.Enabled = false;
            lbwrite.Text = "";
            //var a1 = writeTag("ServerInterfaces.服务器接口_1.数据块_上位机交互.启动", "bool", "False").ToString();
            //var a2 = writeTag("ServerInterfaces.服务器接口_1.数据块_上位机交互.启动", "bool", true)?.ToString();
            //var a3 = writeTag("ServerInterfaces.服务器接口_1.数据块_上位机交互.启动", true)?.ToString();

            var res = writeTag(tbTag.Text,tbWrite.Text);
            tbTag.Enabled = true;

            if (res == null) { lbwrite.Text = "写入失败"; return; }

            if (res.ToString() == "Good")
                lbwrite.Text = "写入成功";


            return;

            List<String> values = new List<string>();
            List<String> nodeIdStrings = new List<string>();
            nodeIdStrings.Add(tbNodeID.Text);
            values.Add(lbwrite.Text);

            try
            {
                WriteValues(values, nodeIdStrings);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }

        }

        private void button3_Click(object sender, EventArgs e)
        {

            button3.Enabled = false; 
            tbServer.ReadOnly=true;

            string opcAddr = tbServer.Text;

            Task.Run(() =>
            {
                // 设置20S连接超时,无需用户名跟密码
                //initClient(opcAddr,20000,null,null, (success, msg) =>
                //{
                //    if (success) setCurrentSessionMsg("连接成功");
                //    else
                //    {
                //        setCurrentSessionMsg("连接失败： " + msg);

                //        button3.Enabled = true;
                //        tbServer.ReadOnly = false;

                //    }
                //});

                // 设置20S连接超时,用于访问服务器的用户名跟密码   serverUrl = "opc.tcp://192.168.31.199:4840";
                initClient(opcAddr, 20000, "admin", "wizard", (success, msg) =>
                {
                    if (success) 
                    {
                        setCurrentSessionMsg("连接成功");

                        browseControl1.Server = new UAClientHelperAPI();
                        browseControl1.Server.Session = session;

                        browseControl1.Browse(null); //查看所有节点
                        groupBox1.Enabled = true;
                    }
                    else
                    {
                        setCurrentSessionMsg("连接失败： " + msg);

                        button3.Enabled = true;
                        tbServer.ReadOnly = false;

                    }
                });

            });

        }

        private void button7_Click(object sender, EventArgs e)
        {
            button7.Enabled = false;
            timer1.Enabled= true;

  
        }

        private void timer1_Tick(object sender, EventArgs e)
        {


            Task.Run(() =>
            {


                if (session == null) return;
                if (plcItemList.Count == 0) return;

                List<String> nodeIdStrings = new List<String>();

                for (int i = 0; i < plcItemList.Count; i++)
                {
                    nodeIdStrings.Add(plcItemList[i].nodeID);
                }

                try
                {

                    List<String> Values = ReadValues(nodeIdStrings);
                    if (Values.Count > 0)
                    {
                        for (int i = 0; i < plcItemList.Count; i++)
                        {
                            plcItemList[i].value = Values[i];
                            plcItemList[i].updateTime =DateTime.Now; 
                        }
                    }
                    dataGridView1.Invalidate(); //更新显示

                }
                catch (Exception ex)
                {
                    //读取失败
                }


            });




        }

        private void button4_Click(object sender, EventArgs e)
        {

            tbNodeID.Enabled = false;
            lbread.Text = "";

            List<String> Value;
            try
            {
               Value = ReadValues(new List<String>() { tbNodeID.Text });
            }
            catch (Exception ex) {

                tbNodeID.Enabled = true;
                lbread.Text = "读取失败"+ex.Message; return;
            }

            tbNodeID.Enabled = true;

            lbread.Text = Value[0];



        }

        private void button5_Click(object sender, EventArgs e)
        {

            tbNodeID.Enabled = false;
            lbwrite.Text = "";


            try
            {
                WriteValues(new List<String>() { tbWrite.Text }, new List<String>() { tbNodeID.Text });
            }
            catch (Exception ex)
            {

                tbNodeID.Enabled = true;

                lbread.Text = "写入失败"+ex.Message; return;
            }





            tbNodeID.Enabled = true;


            lbwrite.Text = "写入成功";




        }
    }
}
